#ifndef __MATH_ROTATION_H__
#define __MATH_ROTATION_H__

/*
===============================================================================

Describes a complete rotation in degrees about an abritray axis.
A local rotation matrix is stored for fast rotation of multiple points.

NOTE: angles are understood as indirect space orientation

===============================================================================
*/

namespace inf {

    class Angles;
    class Quat;
    class Mat3;

    class Rotation {

        friend class Angles;
        friend class Quat;
        friend class Mat3;

    public:
        Rotation( void );
        Rotation( const Vec3 &rotationOrigin, const Vec3 &rotationVec, const float rotationAngle );

        void				set( const Vec3 &rotationOrigin, const Vec3 &rotationVec, const float rotationAngle );
        void				set_origin( const Vec3 &rotationOrigin );
        void				set_Vec( const Vec3 &rotationVec );					// has to be normalized
        void				set_Vec( const float x, const float y, const float z );	// has to be normalized
        void				set_angle( const float rotationAngle );
        void				scale( const float s );
        void				recalculate_matrix( void );
        const Vec3 &		get_origin( void ) const;
        const Vec3 &		get_Vec( void ) const;
        float				get_angle( void ) const;

        Rotation			operator-() const;										// flips rotation
        Rotation			operator*( const float s ) const;						// scale rotation
        Rotation			operator/( const float s ) const;						// scale rotation
        Rotation &			operator*=( const float s );							// scale rotation
        Rotation &			operator/=( const float s );							// scale rotation
        Vec3				operator*( const Vec3 &v ) const;						// rotate vector

        friend Rotation		operator*( const float s, const Rotation &r );		// scale rotation
        friend Vec3			operator*( const Vec3 &v, const Rotation &r );		// rotate vector
        friend Vec3 &		operator*=( Vec3 &v, const Rotation &r );			// rotate vector

        Angles				to_Angles( void ) const;
        Quat				to_Quat( void ) const;
        const Mat3 &		to_Mat3( void ) const;
        Mat4				to_Mat4( void ) const;
        Vec3				to_angular_velocity( void ) const;

        void				rotate_point( Vec3 &point ) const;

        void				normalize_180( void );
        void				normalize_360( void );

    private:
        Vec3				origin;			// origin of rotation
        Vec3				vec;			// normalized vector to rotate around
        float				angle;			// angle of rotation in degrees
        mutable Mat3		axis;			// rotation axis
        mutable bool		axisValid;		// true if rotation axis is valid
    };


    INF_INLINE Rotation::Rotation( void ) {
    }

    INF_INLINE Rotation::Rotation( const Vec3 &rotationOrigin, const Vec3 &rotationVec, const float rotationAngle ) {
        origin = rotationOrigin;
        vec = rotationVec;
        angle = rotationAngle;
        axisValid = false;
    }

    INF_INLINE void Rotation::set( const Vec3 &rotationOrigin, const Vec3 &rotationVec, const float rotationAngle ) {
        origin = rotationOrigin;
        vec = rotationVec;
        angle = rotationAngle;
        axisValid = false;
    }

    INF_INLINE void Rotation::set_origin( const Vec3 &rotationOrigin ) {
        origin = rotationOrigin;
    }

    INF_INLINE void Rotation::set_Vec( const Vec3 &rotationVec ) {
        vec = rotationVec;
        axisValid = false;
    }

    INF_INLINE void Rotation::set_Vec( float x, float y, float z ) {
        vec[0] = x;
        vec[1] = y;
        vec[2] = z;
        axisValid = false;
    }

    INF_INLINE void Rotation::set_angle( const float rotationAngle ) {
        angle = rotationAngle;
        axisValid = false;
    }

    INF_INLINE void Rotation::scale( const float s ) {
        angle *= s;
        axisValid = false;
    }

    INF_INLINE void Rotation::recalculate_matrix( void ) {
        axisValid = false;
        to_Mat3();
    }

    INF_INLINE const Vec3 &Rotation::get_origin( void ) const {
        return origin;
    }

    INF_INLINE const Vec3 &Rotation::get_Vec( void ) const  {
        return vec;
    }

    INF_INLINE float Rotation::get_angle( void ) const  {
        return angle;
    }

    INF_INLINE Rotation Rotation::operator-( void ) const {
        return Rotation( origin, vec, -angle );
    }

    INF_INLINE Rotation Rotation::operator*( const float s ) const {
        return Rotation( origin, vec, angle * s );
    }

    INF_INLINE Rotation Rotation::operator/( const float s ) const {
        assert( s != 0.0f );
        return Rotation( origin, vec, angle / s );
    }

    INF_INLINE Rotation &Rotation::operator*=( const float s ) {
        angle *= s;
        axisValid = false;
        return *this;
    }

    INF_INLINE Rotation &Rotation::operator/=( const float s ) {
        assert( s != 0.0f );
        angle /= s;
        axisValid = false;
        return *this;
    }

    INF_INLINE Vec3 Rotation::operator*( const Vec3 &v ) const {
        if ( !axisValid ) {
            to_Mat3();
        }
        return ((v - origin) * axis + origin);
    }

    INF_INLINE Rotation operator*( const float s, const Rotation &r ) {
        return r * s;
    }

    INF_INLINE Vec3 operator*( const Vec3 &v, const Rotation &r ) {
        return r * v;
    }

    INF_INLINE Vec3 &operator*=( Vec3 &v, const Rotation &r ) {
        v = r * v;
        return v;
    }

    INF_INLINE void Rotation::rotate_point( Vec3 &point ) const {
        if ( !axisValid ) {
            to_Mat3();
        }
        point = ((point - origin) * axis + origin);
    }


}   // ::inf

#endif /* !__MATH_ROTATION_H__ */
